/* Hey EMACS -*- linux-c -*- */
/* $Id$ */

/*  libtifiles - file format library, a part of the TiLP project
 *  Copyright (C) 1999-2005  Romain Lievin
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software Foundation,
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/*
	IntelHex format reader/writer for TI8X FLASH calculators.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "stdints2.h"
#include "macros.h"
#include "intelhex.h"
#include "export2.h"

/* Constants */

#define HEX_DATA	0x00	// data packet
#define HEX_END		0x01	// end of section (1 for app, 3 for OS)
#define HEX_PAGE	0x02	// page change
#define HEX_EOF		0x03	// end of file (custom)

#define PKT_MAX		32		// 32 bytes max
#define BLK_MAX		16384	// 16KB max


/* TI8X+ FLASH files contains text data. */
static uint8_t read_byte(FILE * f)
{
  unsigned int b;

  if (fscanf(f, "%02X", &b) < 1)
    b = 0; /* FIXME: 0 is better than random garbage, but real error handling needed! */
  return b;
}

/*
	hex_packet_read:
	@f : file descriptor
	@size : size of packet (field #1)
	@addr : addr of packet (field #2)
	@type : type of packet (field #3)
	@data : data of packet (field after #3), 32 bytes max

	Read an IntelHexa block from FLASH file like these:
	- ': 10 0000 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 00 CR/LF'
	- ': 00 0000 01 FF'
	- ': 02 0000 02 0000 FC'

	Returns : 0 if success, a negative value otherwise.
*/
static int hex_packet_read(FILE *f, uint8_t *size, uint16_t *addr, uint8_t *type, uint8_t *data)
{
  int c, i;
  uint8_t sum, checksum;

  sum = 0;
  c = fgetc(f);
  if (c != ':')
  {
	  printf("Unexpected char: <%c> = %02X\n", c, c);
	  return -1;
  }

  *size = read_byte(f);
  *addr = read_byte(f) << 8;
  *addr |= read_byte(f);
  *type = read_byte(f);

  if(*size > PKT_MAX)
	  return -2;

  sum = *size + MSB(*addr) + LSB(*addr) + *type;

  for (i = 0; i < *size; i++) 
  {
    data[i] = read_byte(f);
    sum += data[i];
  }

  checksum = read_byte(f);	// verify checksum of block
  if (LSB(sum + checksum))
    return -3;

  {
	  // check for end of file without mangling data checksum
	  int c1, c2, c3;
	  long pos = ftell(f);

	  c1 = fgetc(f);
	  c2 = fgetc(f);
	  c3 = fgetc(f);	//EOF checking is set to keep compatibility with old generated FLASH files (buggy)

	  if(((c1 != 0x0d) && (c2 != 0x0a)) || (c3 == EOF))	
	  {
		// end of file
		*type = HEX_EOF;
		fseek(f, pos, SEEK_SET);
		return 0;
	  }

	  fseek(f, pos+2, SEEK_SET);
  }

  return 0;
}

/*
	hex_block_read:
	@f : file descriptor
	@size : size of block
	@addr : address of block
	@type : a flag (0x80 or 0x00)
	@page : page of block	
	@data : the buffer where block is placed (16KB max)

	Read a data block (page or segment) from FLASH file. 
	If all args are set to NULL, this resets the parser.

	Returns : 0 if success, EOF if end of file has been reached.
*/
int hex_block_read(FILE *f, uint16_t *size, uint16_t *addr, uint8_t *type, uint8_t *data, uint16_t *page)
{
	static int flag = 0x80;
	static uint16_t flash_page;
	static uint16_t flash_addr;
	int i;
	int new_page = 0;

	// reset condition: all args set to NULL
	if(!size && !addr && !type && !data && !page)
	{
		flag = 0x80;
		flash_page = flash_addr = 0;
		return 0;
	}

	// fill-up buffer with 0xff (flash)
	memset(data, 0xff, BLK_MAX);

	*addr = flash_addr;
	*type = flag;
	*page = flash_page;
	*size = 0;

	// load data
	for (i = 0; i < BLK_MAX; )
	{
		int ret;
		uint8_t pkt_size, pkt_type;
		uint8_t pkt_data[PKT_MAX];
		uint16_t pkt_addr;

		// read packet
		ret = hex_packet_read(f, &pkt_size, &pkt_addr, &pkt_type, pkt_data);
		if(ret < 0)	return ret;

		// new block ? Set address
		if(new_page)
		{
			flash_addr = pkt_addr;
			new_page = 0;
		}

		// returned values
		*addr = flash_addr;
		*type = flag;
		*page = flash_page;
		
		// determine what to do
		switch(pkt_type)
		{
		case HEX_DATA:
			// copy data
			memcpy(&data[i], pkt_data, pkt_size);
			i += pkt_size;
			*size = i;
			break;

		case HEX_END: 
			// new section
			flash_addr = 0;
			flash_page = 0;
			flag ^= 0x80;
			if(i == 0)
				break;
			else
				return 0;

		case HEX_PAGE: 
			// new page
			flash_page = (pkt_data[0] << 8) | pkt_data[1];
			new_page = !0;
			break;

		case HEX_EOF: 
			// end of file
			return EOF;

		default: 
			printf("Unexpected char: <%c> = %02x\n", pkt_type, pkt_type);
			return -1;
		}
	}

	return 0;
}

static int write_byte(uint8_t b, FILE * f)
{
  fprintf(f, "%02X", b);
  return 2;
}

/*
	hex_packet_write:
	@f : file descriptor
	@size : size of packet (field #1)
	@addr : addr of packet (field #2)
	@type : type of packet (field #3)
	@data : data of packet (field after #3), 32 bytes max

	Write an IntelHexa block from FLASH file like these:
	- ': 10 0000 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 00 CR/LF'
	- ': 00 0000 01 FF'
	- ': 02 0000 02 0000 FC'

	Returns : number of chars written to file.
*/
static int hex_packet_write(FILE *f, uint8_t size, uint16_t addr, uint8_t type_, uint8_t *data)
{
  int i;
  int sum;
  int num = 0;
  uint8_t type = (type_ == HEX_EOF ? HEX_END : type_);

  fputc(':', f); num++;
  num += write_byte((uint8_t)size, f);
  num += write_byte(MSB(addr), f);
  num += write_byte(LSB(addr), f);
  num += write_byte(type, f);

  sum = size + MSB(addr) + LSB(addr) + type;
  for (i = 0; i < size; i++) 
  {
    num += write_byte(data[i], f);
    sum += data[i];
  }

  num += write_byte((uint8_t)(0x100 - LSB(sum)), f);

  if(type_ != HEX_EOF)
  {
	  fputc(0x0D, f);	num++;	// CR
	  fputc(0x0A, f); num++;	// LF
  }

  return num;
}

/*
	hex_block_write:
	@f : file descriptor
	@size : size of block
	@addr : address of block
	@type : a flag (0x80 or 0x00)
	@page : page of block	
	@data : the buffer where block is placed (16KB max)
	@extra_bytes : number of additional 0xff bytes to add after the end of the data

	Write a data block (page/segment) to FLASH file. 

	Returns : number of chars written to file.
*/
int hex_block_write(FILE *f, uint16_t size, uint16_t addr, uint8_t type, uint8_t *data, uint16_t page, uint16_t extra_bytes)
{
	int bytes_written = 0;
	static int old_flag = 0x80;
	int n, m;
	uint8_t buf[PKT_MAX];
	int  new_section = 0;

	// write end block
	if(!size && !addr && !type && !data && !page)
		return hex_packet_write(f, 0, 0x0000, HEX_EOF, NULL);

	// new section (FLASH OS only)
	if(old_flag == 0x80 && type == 0x00)
		new_section = !0;

	if(old_flag != type)
	{
		old_flag = type;
		bytes_written += hex_packet_write(f, 0, 0x0000, HEX_END, NULL);
	}

	// write page
	if(!addr && !page && !new_section)
	{
		// no packet if default address (0) & page (0)
	}
	else
	{
		buf[0] = page >> 8;
		buf[1] = page & 0xff;
		bytes_written += hex_packet_write(f, 2, 0x0000, HEX_PAGE, buf);
		new_section = 0;
	}

	// write a block (=page)
	while (size > 0 || extra_bytes > 0)
	{
		n = (size > PKT_MAX ? PKT_MAX : size);
		if (n > 0)
		{
			memcpy(buf, data, n);
			size -= n;
			data += n;
		}

		m = (extra_bytes > PKT_MAX - n ? PKT_MAX - n : extra_bytes);
		if (m > 0)
		{
			memset(buf + n, 0xff, m);
			extra_bytes -= m;
		}

		bytes_written += hex_packet_write(f, n + m, addr, HEX_DATA, buf);
		addr += n + m;
	}
	
	return bytes_written;
}
